package com.doodle.common.data;

import com.doodle.common.utils.Future;
import com.doodle.common.utils.FutureListener;
import com.doodle.common.utils.ThreadPool.Job;

public abstract class AsyncResource<T> implements FutureListener<T> {
	
	public interface JobExecutor<T> {
		Future<T> execute(Job<T> job, FutureListener<T> listener);
		void runForJobDone(Runnable runnable);
	}
	
	/*
	 * Transition Map: INIT -> REQUESTED, RECYCLED REQUESTED -> INIT (cancel),
	 * LOADED, ERROR, RECYCLED LOADED, ERROR -> RECYCLED
	 */
	public static final int STATE_INIT = 0;
	public static final int STATE_REQUESTED = 1;
	public static final int STATE_LOADED = 2;
	public static final int STATE_ERROR = 3;
	public static final int STATE_RECYCLED = 4;

	private int mState = STATE_INIT;
	// mTask is not null only when a task is on the way
	
	private Future<T> mTask;
	
	private final JobExecutor<T> mJobExecutor;
	
	private T mResource;
	
	private Runnable mFinishedRunnable = new Runnable() {
		@Override
		public void run() {
			T resource = get();
			onLoadFinished(resource);
		}
	};
	
	public AsyncResource(JobExecutor<T> jobExecutor) {
		mJobExecutor = jobExecutor;
	}
	
	public T get() {
		return mResource;
	}
	
	@Override
	public void onFutureDone(Future<T> future) {
		synchronized (this) {
			mTask = null;
			mResource = future.get();
			if (mState == STATE_RECYCLED) {
				if (mResource != null) {
					recycleResource(mResource);
					mResource = null;
				}
				mJobExecutor.runForJobDone(mFinishedRunnable);
				return; // don't call callback
			}
			if (future.isCancelled() && mResource == null) {
				if (mState == STATE_REQUESTED) {
					mTask = mJobExecutor.execute(requestResource(), this);
				} else {
					mJobExecutor.runForJobDone(mFinishedRunnable);
				}
				return; // don't call callback
			} else {
				mState = mResource == null ? STATE_ERROR : STATE_LOADED;
			}
		}
		mJobExecutor.runForJobDone(mFinishedRunnable);
	}

	public synchronized void startLoad() {
		if (mState == STATE_INIT) {
			mState = STATE_REQUESTED;
			if (mTask == null) {
				mTask = mJobExecutor.execute(requestResource(), this);
			}
		}
	}

	public synchronized void cancelLoad() {
		if (mState == STATE_REQUESTED) {
			mState = STATE_INIT;
			if (mTask != null) {
				mTask.cancel();
			}
		}
	}

	// Recycle the loader and the resource
	public synchronized void recycle() {
		mState = STATE_RECYCLED;
		if (mResource != null) {
			recycleResource(mResource);
			mResource = null;
		}
		if (mTask != null)
			mTask.cancel();
	}

	public synchronized boolean isRequestInProgress() {
		return mState == STATE_REQUESTED;
	}

	public synchronized boolean isRecycled() {
		return mState == STATE_RECYCLED;
	}
	
	public synchronized int getState() {
		return mState;
	}
	
	public abstract Job<T> requestResource();
	public abstract void onLoadFinished(T resource);
	protected abstract void recycleResource(T resource);
}
